{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# python程序字符界面输出控制\n",
    "[hawksoft 13320218299](http://github.com/xingyongkang)\n",
    "\n",
    "在开发非图形化界面的程序时，用户和计算机之间的交互，只能通过input和print两个函数来完成。用户看到的所有信息，都只能通过print函数输出的字符串来展示。因此对字符转进行各种格式定制，就成为设计出美观界面的唯一手段。对此，python提供了三种方法，分别是：\n",
    "- 直接调用print函数输出\n",
    "- 采用占位符语法构造格式化字符串\n",
    "- 使用str对象的format方法\n",
    "\n",
    "## 1 直接调用print函数输出\n",
    "print函数是唯一进行输出的函数。在输出时，也提供了一些简单的格式控制功能，可以通过不同的参数来进行设置。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 中国 12.0\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = '中国'\n",
    "c = 12.0\n",
    "print(a,b,c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`print`函数只是把每个对象依次以字符串的方式输出，无法对每个对象的输出格式进行严格的设置。如上面的例子中，就没有办法把12.0输出为12.00。事实上，`print`函数在输出每个对象时，就只是调用了每个对象的`__format__`方法，由对象自己负责把自己表示为字符串。\n",
    "\n",
    "另外，`print`函数在输出多个对象时，会自动在每一个对象后面加一个空格，这是合理的，不然相邻的两个对象的字符串会紧密地接在一起，无法分辨。你也可以通过设置参数，使用别的字符作为分割符，这个参数是**“sep”**。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10中国12.0\n",
      "10x中国x12.0\n",
      "10888中国88812.0\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = '中国'\n",
    "c = 12.0\n",
    "print(a, b, c, sep = '')\n",
    "print(a, b, c, sep = 'x')\n",
    "print(a, b, c, sep = '888')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还要注意，`print`函数的每次调用，都会在输出后面加一个换行，这是通过另一个参数**\"end\"**来控制的。默认的设置是`end = '\\n'`，你当然可以将其设定为另外的str对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n",
      "中国\n",
      "10xx中国\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = '中国'\n",
    "c = 12.0\n",
    "print(a)\n",
    "print(b)\n",
    "print(a, end = 'xx')\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " >倒数第二行，我把`end`参数设为了‘xx’,输出发生了变化。\n",
    " \n",
    " 总之，`print`可以总结为：\n",
    "\n",
    "```python\n",
    "print(name1,name2,...,sep=' ', end=' ')\n",
    "```\n",
    "- name1,name2等表示需要输出的对象的名字。print函数会依次把每个对象转换为可显示的str对象，并依次在屏幕上输出。\n",
    "- 参数sep控制当输出多个对象时，对象之间的分隔符。默认为‘ ’，即以空格分割多个对象，可以设置为其它str对象。\n",
    "- 参数end控制每次输出后，最后输出的字符。默认为'\\n',表示每次输出都会换行，可以设置为其它str对象。  \n",
    "\n",
    "直接通过print函数输出，比较简单和直观，但格式控制功能很弱，因此只适合程序员调试程序和学习时使用。在商业开发时，一般要先构造格式化的字符串，然后再调用print函数输出，这就是我们下面要讲的两种方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2 采用占位符语法构造格式化字符串\n",
    "首先看下面的程序："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a= 10 b=中国 c = 12.300000\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = \"中国\"\n",
    "c= 12.3\n",
    "s = 'a= %d b=%s c = %f' % (a,b,c)\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在第4行定义了一个str对象，该对象就是格式化字符串，然后在第5行调用`print`函数将其输出。该字符串的定义语法比较奇怪，但熟悉了也可以接受。定义过程可以看作是一个完形填空问题，分为由**%**分开的两个部分：\n",
    "- 前半部分的字符串，可以看作是题目，用于指定输出的格式，但不完整。其中的每一个以**“%”**开始的几个字符，就是需要填入对象的地方，称为占位符。如上面的‘%d’，’%s‘和’%f‘。\n",
    "- 后半部分是一个由“（ ）”包围的多个对象名，对象之间用逗号隔开。并且对象的数目必须与前面的占位符数目一样，不然会报错。\n",
    "\n",
    "\n",
    "运行时，python会将给定的对象，对应填入到占位符位置，形成一个完整的字符串。\n",
    "\n",
    "注意，在实际使用中，程序员经常将4和5行合并为一行，变为："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a= 10 b=中国 c = 12.300000\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = \"中国\"\n",
    "c= 12.3\n",
    "print('a= %d b=%s c = %f' % (a,b,c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样做并没有什么特殊的意义，唯一的好处就是直观。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在每一个占位符处，可以进一步指定对象输出的格式，即可以对输出做更严格的控制，一般规则如下：\n",
    "  \n",
    "  **%对齐方式 输出宽度.小数点位数 输出类型** \n",
    "  \n",
    "  其中：\n",
    "  - 对齐方式： 符号‘-’表示向左对齐，无符号表示向右对齐，似乎没有向中对齐。\n",
    "  - 输出宽度： 指定输出的字符数目，不足将以空格或0填充\n",
    "  - 小数点位数：保留小数点位数，当然只能是float类型才有意义。\n",
    "  - 输出类型： 用单个字母代表不同的输出类型，具体可以参考下面的表格：\n",
    "  \n",
    "  \n",
    "  \n",
    "| 字母 |       对象类型       | 来自单词           |\n",
    "| :--: | :------------------: | ------------------ |\n",
    "|  d   |        十进制        | <u>d</u>ecimal     |\n",
    "|  f   |      十进制实数      | <u>f</u>loat       |\n",
    "|  s   |        字符串        | <u>s</u>tring      |\n",
    "|  b   |        二进制        | <u>b</u>inary      |\n",
    "|  o   |        八进制        | <u>o</u>ctonary    |\n",
    "|  x   |       十六进制       | <u>h</u>exadecimal |\n",
    "|  #x  | 前面添加0x的十六进制 | hexadecimal        |\n",
    "|  #o  |前面添加0o的八进制    |                    |\n",
    "|  #b  | 前面添加0b的二进制   |                    |\n",
    "\n",
    "  \n",
    "  \n",
    "对照下面的程序体会一下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a= 10         b=        中国 c =      12.30\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = \"中国\"\n",
    "c= 12.3\n",
    "print('a= %-10d b=%10s c = %10.2f' % (a,b,c))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对比前面的print函数直接输出，这种方法的好处是，可以进一步控制字符串的格式，但存在如下问题：\n",
    "- 格式控制还不够丰富。\n",
    "- 语法比较奇怪。（你可能有疑问，对比后面的方法）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3 str对象的format方法\n",
    "首先看下面的代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a= 10  b = 中国 c = 12.30\n"
     ]
    }
   ],
   "source": [
    "a = 10\n",
    "b = \"中国\"\n",
    "c= 12.3\n",
    "s = \"a= {0:d}  b = {1:s} c = {2:.2f}\"\n",
    "s1 = s.format(a,b,c)\n",
    "print(s1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "关键在第3，4和5行。可以看出，这种方法和前面的占位符方式有点像，（不，应该是非常像！）可以分为以下步骤：\n",
    "1. 首先构造一个字符串对象s，该字符串格式也是一个完形填空的题目，不同的只是用 { } 标示出每一个占位符。（比‘%’看着舒服，是不是？）\n",
    "2. 然后调用该对象s的format方法，通过参数传入要输出的每个对象，该方法会返回一个字符串对象，从而获得需要的格式化的字符串。（调用对象的方法，让对象帮忙组装字符串，是不是也很舒服？）\n",
    "3. 最后用print函数输出上面返回的字符串对象。\n",
    "\n",
    "\n",
    "当然，看着舒服只是一方面，该方法在格式控制上更严格和便利。在每一个由{ }指定的占位符中，可以按照下面的格式进行指定：\n",
    "\n",
    "{对象编号**:** 填充字符 对齐方式 **.** 小数点位数 输出类型 }\n",
    "\n",
    "注意它们之间并没有空格，我这里只是为了说明。其中：\n",
    "\n",
    "- 对象编号。format中的参数会被从左到右编上号（当然是从0开始），该数据就指定了本处该插入那个对象。\n",
    "- 填充字符。对齐时，如果字符不够，可用该字符填充。\n",
    "- 对齐方式。**>**向右对齐，**<**向左对齐，**^**向中间对齐。\n",
    "- 小数点位数。即小数点后保留多少位。\n",
    "- 输出类型。用不同的字母，代表不同的输出类型，如下表所示：\n",
    "\n",
    "\n",
    "| 字母 |       对象类型       | 来自单词           |\n",
    "| :--: | :------------------: | ------------------ |\n",
    "|  d   |        十进制        | <u>d</u>ecimal     |\n",
    "|  f   |      十进制实数      | <u>f</u>loat       |\n",
    "|  s   |        字符串        | <u>s</u>tring      |\n",
    "|  b   |        二进制        | <u>b</u>inary      |\n",
    "|  o   |        八进制        | <u>o</u>ctonary    |\n",
    "|  x   |       十六进制       | <u>h</u>exadecimal |\n",
    "|  #x  | 前面添加0x的十六进制 | hexadecimal        |\n",
    "|  #o  |前面添加0o的八进制    |                    |\n",
    "|  #b  | 前面添加0b的二进制   |                    |\n",
    "\n",
    "\n",
    "配合下面的代码，好好体验一下吧。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a=    11 ; 0xb ; 0XB ;0o13 ; 0b1011\n",
      "b = 中国xxxxxxxx\n",
      "c =      12.30\n"
     ]
    }
   ],
   "source": [
    "a = 11\n",
    "b = \"中国\"\n",
    "c= 12.3\n",
    "s = \"a= {0: >5d} ; {0:#x} ; {0:#X} ;{0:#o} ; {0:#b}\\nb = {1:x<10s}\\nc = {2: >10.2f}\"\n",
    "s1 = s.format(a,b,c)\n",
    "print(s1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "另外，在实际使用中，如果定义一个格式字符串，就可以多次用它来组装格式字符串。如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    1  china             100\n",
      "    2  Japan              40\n",
      "    3  Korea              80\n"
     ]
    }
   ],
   "source": [
    "s1 = [1,2,3]\n",
    "s2 =[\"china\",'Japan',\"Korea\"]\n",
    "s3 =[100,40,80]\n",
    "s = \"{0: >5d}  {1:<10s} {2:>10d}\"\n",
    "for i in range(0,3):\n",
    "    ts = s.format(s1[i],s2[i],s3[i])\n",
    "    print(ts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面的例子中，format方法被调用了三次，构造了这个表的三行（这个表的数字没有什么意义，我乱想的）。其实，在调用format时，还有很多灵活地传递参数的方法，如下面的例子："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    1  china             100\n",
      "    2  Japan              40\n",
      "    3  Korea              80\n"
     ]
    }
   ],
   "source": [
    "s1 = [1,'china',100]\n",
    "s2 =[2,'Japan',40]\n",
    "s3 =[3,'Korea',80]\n",
    "s = \"{0: >5d}  {1: <10s} {2:>10d}\"\n",
    "ts = s.format(*s1)\n",
    "print(ts)\n",
    "ts = s.format(*s2)\n",
    "print(ts)\n",
    "ts = s.format(*s3)\n",
    "print(ts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意5行中s1前面的\\*，表示参数s1是一个list，函数就会自动将其中的对象对应到占位符。\n",
    "## 4 总结\n",
    "这三种控制输出的方式，从简单到复杂，适用于不同的环境。但作为python程序员，应该熟练掌握第一种和第三种，第二种完全是一个怪胎（我是说语法极不规范，也不符合面向对象的思想）。第一种用在调试程序中，尽管难看，但可以快速地检查一个或多个对象的值，帮你快速找到bug。第三种用在程序的最终版本中，可以提供优雅的字符化界面。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
